package com.hmdp.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.entity.Shop;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static com.hmdp.utils.RedisConstants.*;

@Slf4j
@Component
public class CacheClient {
    @Resource
    StringRedisTemplate stringRedisTemplate;
    public void set(String key, Object value, Long time, TimeUnit Unit) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, Unit);
    }
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit Unit) {
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(Unit.toSeconds(time)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }
    public <T,ID> T quertWithThrough(String keyprefix, ID id , Class<T> clazz, Function<ID,T> dbFallback, Long time, TimeUnit Unit){
        String key = keyprefix + id;
        String JSON = stringRedisTemplate.opsForValue().get(key);
        T t = null;
        if (StrUtil.isNotBlank(JSON)) {
            t = JSONUtil.toBean(JSON, clazz);
            return t;
        }
        if (JSON != null) {
            return null;
        }
        //根据id数据库查询
        t = dbFallback.apply(id);
        if (t == null) {
            //将空值写入redis
            this.set(key, "", CACHE_NULL_TTL, java.util.concurrent.TimeUnit.MINUTES);
            return null;
        }
        this.set(key, t, time, Unit);
        return t;
    }
    private static final ExecutorService CACHE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10);
    public <T,ID> T quertWithLogicalExpire(String keyprefix, ID id , Class<T> clazz, Function<ID,T> dbFallback, Long time, TimeUnit Unit){
        String key = keyprefix + id;
        String JSON = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isBlank(JSON)) {
            return null;
        }
        RedisData redisData = JSONUtil.toBean(JSON, RedisData.class);
        JSONObject data = (JSONObject) redisData.getData();
        T t = JSONUtil.toBean(data, clazz);
        if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {
            return t;
        }
        String LockKey = LOCK_SHOP_KEY + id;
        boolean islock = lock(LockKey);
        if (islock) {
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    T t1 = dbFallback.apply(id);
                    this.setWithLogicalExpire(key, t1, time, Unit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                finally {
                    unlock(LockKey);
                }
            });
        }
        return t;
    }
    private boolean lock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }
    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }
}

